using System;
using ValidationFramework.Extensions;
using System.Xml;
using System.IO;
using System.Xml.Serialization;
using ValidationFramework.Configuration.Xml;
using ValidationFramework.Configuration;
using ValidationFramework.Reflection;

namespace ValidationFramework.Specifications
{
    /// <summary>
    /// A <see cref="IRuleConfigReader"/> that creates a <see cref="RequiredRule{T}"/> from a <see cref="RuleData"/>.
    /// </summary>
    /// <remarks>
    ///   <see cref="RuleData"/> for default attributes.
    ///   <b>Extra Attributes</b>
    ///   <ul>
    ///     <li>
    ///       <c>initialValue</c> (optional): Used to populate <see cref="ValidationFramework.RequiredRule{T}.InitialValue"/>. Accepted formats for <see cref="DateTime"/> are "dd MMM yyyy HH:mm:ss.ff", "yyyy-MM-ddTHH:mm:ss", "dd MMM yyyy hh:mm tt", "dd MMM yyyy hh:mm:ss tt", "dd MMM yyyy HH:mm:ss", "dd MMM yyyy HH:mm" and "dd MMM yyyy" 
    ///     </li>
    ///   </ul>
    /// </remarks>
    /// <example>
    /// <code lang="xml" title="This example shows an xml configuration for RequiredRuleConfigReader">
    /// <rule errorMessage="hello" 
    /// initialValue="10" 
    /// typeName="RequiredRule"/>
    /// </code>
    /// </example>
    /// <seealso cref="RequiredRule{T}"/>
    /// <seealso cref="ConfigurationService"/>
    public sealed class IsRequiredParser : ISpecificationXmlParser
    {
		public ISpecification GetSpecification(XmlElement SpecificationElement, RuleContext context)
        {
            Guard.ArgumentNotNull(SpecificationElement, "SpecificationElement");

            // Infer the generic type from the handle this Specification is being applied to
            var genericType = context.TypeToValidate;

            // Get initial value. Try xml then string.
            object initialValue = null;
            var initialValueXml = SpecificationElement.InnerXml;
            var initialValueString = SpecificationElement.GetAttribute<string>("initialValue", null);

            if (!string.IsNullOrEmpty(initialValueXml))
            {
                // deserialize the complex object using xml serializer
                var initialValueXmlReader = XmlReader.Create(new StringReader(initialValueXml), null, (XmlParserContext)null);
                if (initialValueXmlReader != null)
                {
                    var objectType = genericType;
                    var xmlSerializer = new XmlSerializer(objectType);
                    initialValue = xmlSerializer.Deserialize(initialValueXmlReader);
                }
            }
            else if (!string.IsNullOrEmpty(initialValueString))
            {
                // Use a string converter to deserialize the type
                var converter = System.ComponentModel.TypeDescriptor.GetConverter(genericType);
                if (!converter.CanConvertFrom(typeof(string)))
                    throw new ArgumentException(string.Format("The supplied string '{0}' cannot be converted to type '{1}'", initialValueString, genericType.Name));

                initialValue = converter.ConvertFromString(initialValueString);
            }

            if (initialValue != null)
            {
                return (ISpecification)typeof(IsRequiredSpec<>).CreateGenericInstance(genericType, new[] { initialValue });
            }
            else
            {
                return (ISpecification)typeof(IsRequiredSpec<>).CreateGenericInstance(genericType, null);
            }
        }
	}
}